/////////////////////////////////////////////////////////////////////////////
// Name:        ax_menu.cpp
// Purpose:     Menu classes
// Author:      Julian Smart
// Modified by:
// Created:     2009-03-20
// RCS-ID:
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#include "wx/wx.h"
#include "wx/xrc/xmlres.h"

#include "wx/filename.h"

#include "wx/arrimpl.cpp"

#include "ax/ax_menu.h"
#include "ax/ax_activator.h"
#include "ax/ax_validator.h"
#include "ax/ax_view.h"
#include "ax/ax_menu_toggle.h"

IMPLEMENT_CLASS( AxMenuItemData, wxEvtHandler )

/*
 * AxMenuItem
 */

IMPLEMENT_DYNAMIC_CLASS( AxMenuItem, wxEvtHandler )

AxMenuItem::~AxMenuItem()
{
    if (m_parent)
        m_parent->RemoveChild(this);

    if (m_subMenu)
    {
        delete m_subMenu;
        m_subMenu = NULL;
    }
    if (m_data)
    {
        delete m_data;
        m_data = NULL;
    }
    if (m_activator)
    {
        delete m_activator;
        m_activator = NULL;
    }
    if (m_validator)
    {
        delete m_validator;
        m_validator = NULL;
    }
}

// Member initialisation
void AxMenuItem::Init()
{
    m_subMenu = NULL;
    m_data = NULL;
    m_activator = NULL;
    m_validator = NULL;
    m_parent = NULL;
    m_style = 0;
    m_enabled = true;
    m_dynamic = false;
}

void AxMenuItem::Copy(const AxMenuItem& item)
{
    if (m_data)
    {
        delete m_data;
        m_data = NULL;
    }
    if (m_activator)
    {
        delete m_activator;
        m_activator = NULL;
    }

    if (item.m_data)
        m_data = item.m_data->Clone();

    if (item.m_activator)
        m_activator = item.m_activator->Clone();

    if (item.m_validator)
        m_validator = item.m_validator->Clone();

    m_id = item.m_id;
    m_style = item.m_style;
    m_label = item.m_label;
    m_kind = item.m_kind;
    m_description = item.m_description;
    m_dynamic = item.m_dynamic;
    m_enabled = item.m_enabled;

    if (m_subMenu)
    {
        delete m_subMenu;
        m_subMenu = NULL;
    }

    if (item.m_subMenu)
    {
        if (m_subMenu)
            delete m_subMenu;
        m_subMenu = new AxMenu(* item.m_subMenu);
        m_subMenu->SetParent(this);
    }
}

bool AxMenuItem::operator==(const AxMenuItem& item) const
{
    return (m_label == item.m_label && m_kind == item.m_kind && m_style == item.m_style && m_dynamic == item.m_dynamic && m_id == item.m_id && m_description == item.m_description);
}

void AxMenuItem::SetActivator(AxActivator* activator)
{
    if (m_activator && m_activator != activator)
        delete m_activator;

    m_activator = activator;
    if (activator) activator->SetMenuItem(this);
}

void AxMenuItem::SetValidator(AxValidator* validator)
{
    if (m_validator && m_validator != validator)
        delete m_validator;

    m_validator = validator;
    if (validator) validator->SetMenuItem(this);
}

// Transfer data using validator, if any.
bool AxMenuItem::TransferDataToMenu(bool recurse)
{
    if (GetValidator())
        GetValidator()->TransferDataToMenu();
    if (recurse && GetSubMenu())
        GetSubMenu()->TransferDataToMenu(recurse);
    return true;
}

// Transfer data using validator, if any.
bool AxMenuItem::TransferDataFromMenu(bool recurse)
{
    if (GetValidator())
        GetValidator()->TransferDataFromMenu();
    if (recurse && GetSubMenu())
        GetSubMenu()->TransferDataFromMenu(recurse);
    return true;
}

void AxMenuItem::SetSubMenu(AxMenu* menu)
{
    m_subMenu = menu;
    if (menu)
        menu->SetParent(this);
}

// Set menu item label spec
void AxMenuItem::SetLabelSpec(const wxString& spec)
{
    AxAutoUpdateActivator* activator = wxDynamicCast(GetActivator(), AxAutoUpdateActivator);
    if (activator)
        activator->SetLabelSpec(spec);
}

wxString AxMenuItem::GetLabelSpec() const
{
    AxAutoUpdateActivator* activator = wxDynamicCast(GetActivator(), AxAutoUpdateActivator);
    if (activator)
        return activator->GetLabelSpec();
    else
        return wxEmptyString;
}

// Set menu item description spec
void AxMenuItem::SetDescriptionSpec(const wxString& spec)
{
    AxAutoUpdateActivator* activator = wxDynamicCast(GetActivator(), AxAutoUpdateActivator);
    if (activator)
        activator->SetDescriptionSpec(spec);
}

wxString AxMenuItem::GetDescriptionSpec() const
{
    AxAutoUpdateActivator* activator = wxDynamicCast(GetActivator(), AxAutoUpdateActivator);
    if (activator)
        return activator->GetDescriptionSpec();
    else
        return wxEmptyString;
}

// Get the zero-based position in the parent menu.
int AxMenuItem::GetPosition() const
{
    if (GetParent())
    {
        int pos = GetParent()->GetItems().Index((AxMenuItem*) this);
        return pos;
    }
    else
        return 0;
}

// Processes the event, or sends it up the hierarchy.
bool AxMenuItem::ProcessEvent(wxEvent& event)
{
    if (wxEvtHandler::ProcessEvent(event))
        return true;

    if (GetActivator() && GetActivator()->ProcessEvent(event))
        return true;

    if (event.IsCommandEvent() && GetParent())
    {
        if (GetParent()->ProcessEvent(event))
            return true;

        if (GetParent()->GetParent() && GetParent()->GetParent()->ProcessEvent(event))
            return true;
    }

    return false;
}

// Gets an integer equivalent identifier, using XRCID
int AxGetId(const wxChar* id)
{
    return wxXmlResource::GetXRCID(id);
}

IMPLEMENT_DYNAMIC_CLASS( AxGoUpMenuItem, AxMenuItem )

AxGoUpMenuItem::AxGoUpMenuItem():
    AxMenuItem(_("Go Up"), _("Go up to the previous menu"),
                        AxIdentifierGoUp, new AxUpActivator())
{
    SetStyle(AxMenuStyleBold);
}

/*
 * AxMenu implementation
 */

IMPLEMENT_DYNAMIC_CLASS( AxMenu, wxEvtHandler )

AxMenuArray AxMenu::sm_menusPendingDeletion;

AxMenu::~AxMenu()
{
    if (m_parent)
        m_parent->SetSubMenu(NULL);

    ClearChildren();
}

// Member initialisation
void AxMenu::Init()
{
    m_parent = NULL;
    m_currentSelection = 0;
    m_isModal = false;
}

void AxMenu::Copy(const AxMenu& menu)
{
    m_id = menu.m_id;
    m_label = menu.m_label;
    m_description = menu.m_description;
    m_currentSelection = menu.m_currentSelection;
    m_isModal = menu.m_isModal;

    m_items = menu.m_items;

    size_t i;
    for (i = 0; i < m_items.GetCount(); i++)
        m_items[i]->SetParent(this);

}

bool AxMenu::operator==(const AxMenu& menu) const
{
    return (m_label == menu.m_label && m_id == menu.m_id && m_description == menu.m_description && m_items.GetCount() == menu.m_items.GetCount());
}

void AxMenu::AddItem(AxMenuItem* item)
{
    m_items.Add(item);
    item->SetParent(this);
}

void AxMenu::RemoveChild(AxMenuItem* item)
{
    size_t i;
    for (i = 0; i < m_items.GetCount(); i++)
    {
        if (m_items[i] == item)
        {
            m_items.RemoveAt(i);
            return;
        }
    }
}

void AxMenu::DeleteChild(AxMenuItem* item)
{
    size_t i;
    for (i = 0; i < m_items.GetCount(); i++)
    {
        if (m_items[i] == item)
        {
            delete m_items[i];
            m_items.RemoveAt(i);
            return;
        }
    }
}

void AxMenu::Unlink()
{
    if (m_parent)
        m_parent->SetSubMenu(NULL);
    m_parent = NULL;
}

void AxMenu::ClearChildren()
{
    while (m_items.GetCount() > 0)
    {
        AxMenuItem* item = m_items[0];
        // Removed from the menu implicitly by deleting the item
        delete item;
    }
}

// Transfer data using validator, if any.
bool AxMenu::TransferDataToMenu(bool recurse)
{
    size_t i;
    for (i = 0; i < m_items.GetCount(); i++)
    {
        m_items[i]->TransferDataToMenu(recurse);
    }
    return true;
}

// Transfer data using validator, if any.
bool AxMenu::TransferDataFromMenu(bool recurse)
{
    size_t i;
    for (i = 0; i < m_items.GetCount(); i++)
    {
        m_items[i]->TransferDataFromMenu(recurse);
    }
    return true;
}

AxMenuItem* AxMenu::FindItemById(const wxString& id) const
{
    size_t i;
    for (i = 0; i < m_items.GetCount(); i++)
    {
        if (m_items[i]->GetId() == id)
            return m_items[i];
    }
    return NULL;
}

AxMenuItem* AxMenu::FindItemByLabel(const wxString& label) const
{
    size_t i;
    for (i = 0; i < m_items.GetCount(); i++)
    {
        if (m_items[i]->GetLabel() == label)
            return m_items[i];
    }
    return NULL;
}

AxMenuItem* AxMenu::GetCurrentItem() const
{
    if (GetCurrentSelection() != -1 && GetCurrentSelection() < (int) GetCount())
        return GetItem(GetCurrentSelection());
    else
        return NULL;
}

void AxMenu::DoDelayedMenuDeletion()
{
    size_t i;
    for (i = 0; i < sm_menusPendingDeletion.GetCount(); i++)
    {
        delete sm_menusPendingDeletion[i];
    }
    sm_menusPendingDeletion.Clear();
}

void AxMenu::ScheduleMenuForDeletion(AxMenu* menu)
{
    sm_menusPendingDeletion.Add(menu);
}

/*
 * AxMenuController implementation
 * This controls how the menu is navigated, using
 * a supplied control. Derive from this class and
 * override functions that indicate when the menu
 * should change. Also, call functions in this object
 * to indicate activation, etc.
 */

IMPLEMENT_CLASS( AxMenuController, wxEvtHandler )

AxMenuController::~AxMenuController()
{
    if (m_menu)
        delete m_menu;
    m_menu = NULL;
    m_currentMenu = NULL;
}

void AxMenuController::Init()
{
    m_menu = NULL;
    m_currentMenu = NULL;
    m_viewManager = NULL;
}

// Set the main menu
void AxMenuController::SetMenu(AxMenu* menu)
{
    if (m_menu)
        delete m_menu;
    m_menu = menu;
}

bool AxMenuController::Activate(AxMenuItem* item)
{
    if (!item->IsEnabled())
        return false;

    AxMenu* newMenu = NULL;

    EmitSpeech(AxSpeechEventActivateItem, AxNavigationHintNone, AxSpeechFlagsPurge);

    // Call activation object to get new view/menu, or traverse static menu
    if (item->GetActivator())
    {
        AxMenu* nextMenu = item->GetActivator()->Activate(this, item);
        if (nextMenu)
            newMenu = nextMenu;
    }
    else if (item->GetSubMenu())
    {
        newMenu = item->GetSubMenu();
    }

    UpdateUI(GetCurrentMenu());

    if (newMenu)
        return ShowMenu(newMenu, item);
    else
        return false;
}

// Show a top-level menu
bool AxMenuController::ShowInitialMenu(AxMenu* menu)
{
    if (menu != GetMenu())
        SetMenu(menu);

    return ShowMenu(menu, NULL);
}

bool AxMenuController::ShowMenu(AxMenu* menu, AxMenuItem* parentItem, AxMenu* childMenu,
                                AxNavigationHint hint)
{
    AxMenu* menuToShow = menu;
    if (childMenu)
        menuToShow = childMenu;

    if (!OnChangeMenuPre(GetCurrentMenu(), menuToShow))
    {
        // Potential memory leak if menu not attached to main menu.
        // Delete menu here if has no parent (and it's not the top-level
        // menu).

        if (menu != GetMenu() && !menu->GetParent())
        {
            menu->Unlink();
            AxMenu::ScheduleMenuForDeletion(menu);
        }
        return false;
    }
    
    // Hook this menu onto the current menu item if not already done
    // FIXME: maybe this isn't right. What if some action causes a menu
    // to be shown, when the current item has a static submenu? It then
    // won't be linked. Could have a temporary menu pointer in the view manager,
    // though we wouldn't be able to link back to the last menu without dealing
    // with the possibility of that menu being deleted.
    
    if (menu && !menu->GetParent() && GetMenu() != menu && GetCurrentMenu() != menu)
    {
        if (!parentItem)
            parentItem = GetMenuSelection();
        
        if (parentItem && menu != parentItem->GetSubMenu())
        {
            if (parentItem->GetSubMenu() && parentItem->IsDynamic())
            {
                parentItem->GetSubMenu()->Unlink();

                AxMenu::ScheduleMenuForDeletion(parentItem->GetSubMenu());
            }

            if (parentItem->GetSubMenu() == NULL)
            {
                parentItem->SetSubMenu(menu);
            }
            else // if (!menu->IsKindOf(CLASSINFO(AxModalMenu)))
            {
                wxLogError(wxT("Sorry, this menu could not be linked to the current item and there will be a memory leak.\n\
Probably your Activator object created a new menu for a second time without marking the menu item dynamic.\n\
If your menu item is static, try returning item->GetSubMenu() from Activate() if non-NULL."));
            }
        }
    }
    
    SetCurrentMenu(menuToShow);

    wxString title = GetCurrentMenu()->GetLabel();
    SetTitle(title);
    RefreshMenu(GetCurrentMenu(), hint);

    OnChangeMenuPost(menuToShow);

    return true;
}

bool AxMenuController::Select(AxMenuItem* item)
{
    bool success = true;
    if (item->GetActivator())
        success = item->GetActivator()->Select(this, item);
    else
    {
        if (GetViewManager() && GetViewManager()->GetDescriptionCtrl())
        {
            GetViewManager()->GetDescriptionCtrl()->SetValue(item->GetDescription());
        }
    }
 
    UpdateUI(GetCurrentMenu());
 
    EmitSpeech(AxSpeechEventChangeSelection, AxNavigationHintNone, AxSpeechFlagsPurge);

    return true;
}

bool AxMenuController::Describe(AxMenuItem* item, int describeAtVerbosity)
{
    UpdateUI(GetCurrentMenu());

    AxSpeechEvent event;

    // This is not the verbosity of this event, it's the verbosity with which
    // to describe the event.
    event.SetVerbosityPolicy(describeAtVerbosity);
    event.SetSpeechEventType(AxSpeechEventDescribeItem);
    event.SetFlags(AxSpeechFlagsPurge);
    event.SetMenu(item->GetParent());
    event.SetMenuItem(item);

    EmitSpeech(event);

    return true;
}

bool AxMenuController::DescribeCurrentContext(int describeAtVerbosity)
{
    UpdateUI(GetCurrentMenu());

    AxSpeechEvent event;

    // This is not the verbosity of this event, it's the verbosity with which
    // to describe the event.
    event.SetVerbosityPolicy(describeAtVerbosity);
    event.SetSpeechEventType(AxSpeechEventDescribeContext);
    event.SetFlags(AxSpeechFlagsPurge);
    event.SetMenu(GetCurrentMenu());
    AxMenuItem* menuItem = NULL;
    if (GetCurrentMenu())
        menuItem = GetCurrentMenu()->GetCurrentItem();
    event.SetMenuItem(menuItem);

    EmitSpeech(event);
    
    return true;
}

bool AxMenuController::DescribeCurrentItem(int describeAtVerbosity)
{
    if (GetCurrentMenu())
    {
        AxMenuItem* menuItem = GetCurrentMenu()->GetCurrentItem();
        if (menuItem)
        {
            Describe(menuItem, describeAtVerbosity);
            return true;
        }
    }
    
    return false;
}

bool AxMenuController::GoUp()
{
    SetMenuFocus();

    // If we can't go up (possibly because the current menu is modal),
    // then find a cancel menu item if there is one.
    if (!CanGoUp() && GetCurrentMenu() && GetCurrentMenu()->IsModal())
    {
        AxMenuItem* item = GetCurrentMenu()->FindItemById(AxIdentifierCancel);
        if (item)
        {
            return Activate(item);
        }
    }

    if (CanGoUp())
    {
        AxMenu* menu = GetCurrentMenu()->GetParent()->GetParent();
        if (ShowMenu(menu, NULL, NULL, AxNavigationHintUp))
        {
            if (menu->GetParent() && menu->GetParent()->GetActivator())
                menu->GetParent()->GetActivator()->Reactivate(this, menu->GetParent());
            return true;
        }
        else
            return false;
    }
    else
        return false;
}

bool AxMenuController::GoHome()
{
    SetMenuFocus();

    // TODO: notify current view we're changing to a different menu
    // We need to be able to figure out whether a menu change will
    // change the view.
    if (CanGoHome())
    {
        GetViewManager()->ShowHomeView();
        return ShowMenu(GetMenu(), NULL, NULL, AxNavigationHintHome);
    }
    else
        return false;
}

bool AxMenuController::CanGoUp() const
{
    return GetCurrentMenu() && GetCurrentMenu()->GetParent() &&
        !GetCurrentMenu()->IsModal();
}

bool AxMenuController::CanGoHome() const
{
    return GetViewManager() && GetMenu() && /* GetCurrentMenu() != GetMenu() && */
        !GetViewManager()->IsModal();
}

// Override if something needs to be done before the menu context changes, e.g.
// saving an edit. If this returns false, it vetoes the menu change.
bool AxMenuController::OnChangeMenuPre(AxMenu* currentMenu, AxMenu* newMenu)
{
    if (GetViewManager() && GetViewManager()->GetActiveView() && !GetViewManager()->GetActiveView()->OnChangeMenuPre(currentMenu, newMenu))
        return false;

    return true;
}

// Override if something needs to be done before the menu context changes, e.g.
// saving an edit. If this returns false, it vetoes the menu change.
bool AxMenuController::OnChangeMenuPost(AxMenu* WXUNUSED(newMenu))
{
    return true;
}

// Override to do something just before the menu is refreshed.
bool AxMenuController::OnBeforeDisplay(AxMenu* newMenu)
{
    size_t i;
    for (i = 0; i < newMenu->GetCount(); i++)
    {
        if (newMenu->GetItem(i)->GetActivator())
            newMenu->GetItem(i)->GetActivator()->BeforeDisplay(this, newMenu->GetItem(i));
    }
    return true;
}

// Update the current menu UI
bool AxMenuController::UpdateUI(AxMenu* menu)
{
    size_t i;
    for (i = 0; i < menu->GetCount(); i++)
    {
        if (menu->GetItem(i)->GetActivator())
            menu->GetItem(i)->GetActivator()->UpdateUI(this, menu->GetItem(i));
    }
    return true;
}

// Emit speech
bool AxMenuController::EmitSpeech(const wxString& speechEventType, const wxString& text,
                                  AxNavigationHint hint, int flags)
{
    AxSpeechEvent event;
    event.SetSpeechEventType(speechEventType);
    event.SetText(text);
    event.SetMenu(GetCurrentMenu());
    event.SetNavigationHint(hint);
    event.SetFlags(flags);

    AxMenuItem* menuItem = NULL;
    if (GetCurrentMenu())
        menuItem = GetCurrentMenu()->GetCurrentItem();
    event.SetMenuItem(menuItem);

    return DoEmitSpeech(event);
}

// Emit speech
bool AxMenuController::EmitSpeech(const wxString& speechEventType, AxNavigationHint hint,
                                  int flags)
{
    return EmitSpeech(speechEventType, wxEmptyString, hint, flags);
}

// Speech emission implementation.
bool AxMenuController::DoEmitSpeech(AxSpeechEvent& event)
{
    if (GetViewManager())
        return GetViewManager()->GetSpeech().EmitSpeech(event);
    else
        return false;
}

// Perform idle processing
bool AxMenuController::IdleProcessing()
{
    if (GetCurrentMenu())
        UpdateUI(GetCurrentMenu());

    if (GetViewManager())
        GetViewManager()->GetSpeech().ProcessPendingSpeechEvents();

    // FIXME: Problem - if a function causes idle events to be processed,
    // the menus are deleted, and then speech events are scheduled,
    // we'll get dangling pointers.

    AxMenu::DoDelayedMenuDeletion();

    return true;
}

